﻿namespace NJQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// Defines a grid for snapping.
	/// </summary>
	[TypeConverter(typeof(GridConverter))]
	public struct Grid : IEquatable<Grid>, IComplexProperty {


		#region Static Readonly Fields: Standard values

		/// <summary>
		/// Instructs jQuery UI widget not to use a grid.
		/// </summary>
		public static readonly Grid None = new Grid();

		#endregion


		#region Static Methods

		/// <summary>
		/// Converts a string representation to its <see cref="Grid"/> equivalent.
		/// </summary>
		/// <param name="text">A <see cref="String"/> containing the value to convert.</param>
		/// <param name="result">The parsed value when the conversion succeeds, or a default instance otherwise.</param>
		/// <returns>Indicates whether the conversion succeeded.</returns>
		public static bool TryParse(string text, out Grid result) {

			if(string.IsNullOrEmpty(text)) {
				result = None;
				return true;
			}

			int intValue;
			if(int.TryParse(text, out intValue)) {
				result = new Grid(intValue);
				return true;
			}

			string[] splitText = text.Split(',');
			if(splitText.Length == 2) {
				int x; int y;
				if(int.TryParse(splitText[0], out x) && int.TryParse(splitText[1], out y)) {
					result = new Grid(x, y);
					return true;
				}
			}

			result = None;
			return false;

		}

		#endregion


		#region Fields

		private readonly int x;
		private readonly int y;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new NJQuery.Grid value identical values for x and y.
		/// </summary>
		/// <param name="value">The value to use for x and y.</param>
		/// <exception cref="ArgumentOutOfRangeException">Value must positive.</exception>
		public Grid(int value) {
			if(value <= 0) throw new ArgumentOutOfRangeException("value", "Value must be positive.");

			x = y = value;
		}

		/// <summary>
		/// Initializes a new <see cref="Grid"/> value using separately specified x and y values.
		/// </summary>
		/// <param name="x">The horizontal size of the grid.</param>
		/// <param name="y">The vertical size of the grid.</param>
		/// <exception cref="ArgumentOutOfRangeException">Both values must positive.</exception>
		public Grid(int x, int y) {
			if(x <= 0) throw new ArgumentOutOfRangeException("x", "Value must be positive.");
			if(y <= 0) throw new ArgumentOutOfRangeException("y", "Value must be positive.");

			this.x = x;
			this.y = y;
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets the horizontal value of the grid.
		/// </summary>
		public int X {
			get { return x; }
		}

		/// <summary>
		/// Gets the vertical value of the grid.
		/// </summary>
		public int Y {
			get { return y; }
		}

		/// <summary>
		/// Gets whether or not the x and y values are identical.
		/// </summary>
		public bool IsSquare {
			get { return IsSet && x == y; }
		}

		/// <summary>
		/// Gets whether or not a grid has been defined.
		/// </summary>
		public bool IsSet {
			get { return x != 0 && y != 0; }
		}

		#endregion


		#region Methods: Object management

		/// <summary>
		/// Determines whether or not the specified object is equal to the current <see cref="Grid"/> value.
		/// </summary>
		/// <param name="obj">The <see cref="Object"/> to compare.</param>
		/// <returns>Whether or not the provided object is equal.</returns>
		public override bool Equals(object obj) {
			if(obj is Grid) return this == (Grid)obj;
			return false;
		}

		/// <summary>
		/// Determines whether or not the specified Grid is equal to the current <see cref="Grid"/> value.
		/// </summary>
		/// <param name="other">The <see cref="Grid"/> to compare.</param>
		/// <returns>Whether or not the provided value is equal.</returns>
		public bool Equals(Grid other) {
			return this == other;
		}

		/// <summary>
		/// Serves as a hash function for <see cref="Grid"/>.
		/// </summary>
		/// <returns>The hash value associated with the contained data.</returns>
		public override int GetHashCode() {
			//TODO: Have TheMuuj check it out
			return x.GetHashCode() & ~y.GetHashCode();
		}

		/// <summary>
		/// Returns a <see cref="String"/> that represents the current <see cref="Grid"/> value.
		/// </summary>
		/// <returns>The string representation.</returns>
		public override string ToString() {
			if(!IsSet) return string.Empty;
			if(IsSquare) return x.ToString(CultureInfo.InvariantCulture);
			return string.Format(CultureInfo.InvariantCulture, "{0},{1}", x, y);
		}

		/// <summary>
		/// Returns a literal JavaScript value used to represent the server value on the client.
		/// </summary>
		/// <returns>The client script value.</returns>
		public string ToClientSource() {
			if(!IsSet) return false.ToClientSource();
			if(IsSquare) return x.ToClientSource();
			return new int[] { x, y }.ToClientSource();
		}

		#endregion


		#region Operators

		/// <summary>
		/// Compares the equality of two <see cref="Grid"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are equal.</returns>
		public static bool operator ==(Grid value0, Grid value1) {
			return value0.x == value1.x && value0.y == value1.y;
		}

		/// <summary>
		/// Compares the inequality of two <see cref="Duration"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are distinct.</returns>
		public static bool operator !=(Grid value0, Grid value1) {
			return value0.x != value1.x || value0.y != value1.y;
		}

		#endregion


	}

}